<?php

declare(strict_types=1);

namespace Phpben\Imi\Validate\Aop;

use Imi\Aop\Annotation\Aspect;
use Imi\Aop\Annotation\PointCut;
use Imi\Aop\PointCutType;
use Imi\Bean\Annotation\AnnotationManager;
use Imi\Bean\BeanFactory;
use Imi\RequestContext;
use Imi\Server\Http\Message\Proxy\ResponseProxy;
use Imi\Util\ClassObject;
use Imi\Util\Http\Consts\MediaType;
use Imi\Util\Http\Consts\ResponseHeader;
use Imi\Util\Stream\MemoryStream;
use Phpben\Imi\Validate\Annotation\Validate;
use Imi\Aop\AroundJoinPoint;
use Imi\Aop\Annotation\Around;
use Phpben\Imi\Validate\ValidateException;

/**
 * @Aspect
 */
class ValidateAop
{
    /**
     * 验证器处理
     * @PointCut(type=PointCutType::ANNOTATION,allow={Validate::class})
     * @Around
     */
    public function parseValidation(AroundJoinPoint $joinPoint)
    {
        $controller = $joinPoint->getTarget();
        $className = BeanFactory::getObjectClass($controller);
        $methodName = $joinPoint->getMethod();
        $annotation = AnnotationManager::getMethodAnnotations($className, $methodName, Validate::class)[0] ?? [];
        if ($annotation) {
            $class = $annotation->class;
            if (!$class) {
                $_class = explode('\\', $className);
                $_count_class = count($_class);
                $controllerName = $_class[$_count_class - 1];
                unset($_class[$_count_class - 1]);
                $class = trim(str_replace('\\Controller', '\\Validate', implode('\\', $_class)), '\\') . '\\' . str_replace('Controller', 'Validate', $controllerName);
            }
            if (!class_exists($class)) {
                throw new ValidateException($class . ' is not defined');
            }
            $request = \Imi\RequestContext::get('request');
            $method = $request->getMethod();
            if (strtoupper($method) == 'GET') {
                $params = (array)$request->get();
            } else {
                $params = (array)$request->getParsedBody();
            }
            if ($annotation->security) {
                $filter = ['trim', 'strip_tags', 'htmlspecialchars'];
                foreach ($filter as $k => $v) {
                    foreach ($params as $a => &$b) {
                        is_string($b) && $b = call_user_func($v, $b);
                    }
                }
            }
            try {
                $validate = new $class;
                $scene = $annotation->scene;
                if ($validate->getScenes() && isset($validate->getScenes()[$methodName]) && !$scene) {
                    $scene = $methodName;
                }
                if (!($scene ? $validate->scene($scene)->check($params) : $validate->check($params))) {
                    $error = $validate->getError();
                }
            } catch (ValidateException $e) {
                $error = $e->getError();
            }
            if (isset($error) && $error) {
                return $this->response([
                    'code' => 500,
                    'message' => $error
                ]);
            }
            if ($annotation->fitler) {
                $validateRules = $validate->getRules();
                foreach ($params as $k => $v) {
                    if (!isset($validateRules[$k]) && !in_array($k, $validateRules)) {
                        unset($params[$k]);
                    }
                }
            }
            $args = ClassObject::convertArgsToKV($className, $methodName, $joinPoint->getArgs());
            $params = new \Imi\Util\LazyArrayObject($params);
            $args[$annotation->var] = $params;
            $args = array_values($args);
            $joinPoint->setArgs($args);
        }
        return $joinPoint->proceed($args);
    }

    /**
     * 拦截响应
     * @param array $error
     */
    private function response(array $error)
    {
        $response = new ResponseProxy;
        return $response->withHeader(ResponseHeader::CONTENT_TYPE, MediaType::APPLICATION_JSON_UTF8)->withStatus(200)->withBody(new MemoryStream(json_encode([
            'code' => $error['code'],
            'message' => $error['message']
        ], 256 | 64)));
    }
}